//
// ssl/stream.hpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2017 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef ASIO_SSL_STREAM_HPP
#define ASIO_SSL_STREAM_HPP

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
#pragma once
#endif  // defined(_MSC_VER) && (_MSC_VER >= 1200)

#include <asio/detail/config.hpp>

#include "mongo/util/net/ssl/apple.hpp"
#include "mongo/util/net/ssl/context.hpp"
#include "mongo/util/net/ssl/detail/buffered_handshake_op.hpp"
#include "mongo/util/net/ssl/detail/handshake_op.hpp"
#include "mongo/util/net/ssl/detail/io.hpp"
#include "mongo/util/net/ssl/detail/read_op.hpp"
#include "mongo/util/net/ssl/detail/shutdown_op.hpp"
#include "mongo/util/net/ssl/detail/stream_core.hpp"
#include "mongo/util/net/ssl/detail/write_op.hpp"
#include "mongo/util/net/ssl/stream_base.hpp"
#include <asio/async_result.hpp>
#include <asio/detail/buffer_sequence_adapter.hpp>
#include <asio/detail/handler_type_requirements.hpp>
#include <asio/detail/noncopyable.hpp>
#include <asio/detail/type_traits.hpp>

#include <asio/detail/push_options.hpp>

namespace asio {
namespace ssl {

/// Provides stream-oriented functionality using SSL.
/**
 * The stream class template provides asynchronous and blocking stream-oriented
 * functionality using SSL.
 *
 * @par Thread Safety
 * @e Distinct @e objects: Safe.@n
 * @e Shared @e objects: Unsafe. The application must also ensure that all
 * asynchronous operations are performed within the same implicit or explicit
 * strand.
 *
 * @par Example
 * To use the SSL stream template with an ip::tcp::socket, you would write:
 * @code
 * asio::io_context io_context;
 * asio::ssl::context ctx(asio::ssl::context::sslv23);
 * asio::ssl::stream<asio:ip::tcp::socket> sock(io_context, ctx);
 * @endcode
 *
 * @par Concepts:
 * AsyncReadStream, AsyncWriteStream, Stream, SyncReadStream, SyncWriteStream.
 */
template <typename Stream>
class stream : public stream_base, private noncopyable {
public:
/// The native handle type of the SSL stream.
#if MONGO_CONFIG_SSL_PROVIDER == MONGO_CONFIG_SSL_PROVIDER_WINDOWS
    typedef PCtxtHandle native_handle_type;
#elif MONGO_CONFIG_SSL_PROVIDER == MONGO_CONFIG_SSL_PROVIDER_OPENSSL
    typedef SSL* native_handle_type;
#elif MONGO_CONFIG_SSL_PROVIDER == MONGO_CONFIG_SSL_PROVIDER_APPLE
    typedef ::SSLContextRef native_handle_type;
#else
#error "Unknown SSL Provider"
#endif

    /// The type of the next layer.
    typedef typename remove_reference<Stream>::type next_layer_type;

    /// The type of the lowest layer.
    typedef typename next_layer_type::lowest_layer_type lowest_layer_type;

    /// The type of the executor associated with the object.
    typedef typename lowest_layer_type::executor_type executor_type;

#if defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION)
    /// Construct a stream.
    /**
     * This constructor creates a stream and initialises the underlying stream
     * object.
     *
     * @param arg The argument to be passed to initialise the underlying stream.
     *
     * @param ctx The SSL context to be used for the stream.
     */
    template <typename Arg>
    stream(Arg&& arg, context& ctx, const std::string& remoteHostName)
        : next_layer_(ASIO_MOVE_CAST(Arg)(arg)),
          core_(ctx.native_handle(),
                remoteHostName,
                next_layer_.lowest_layer().get_executor().context()) {}
#else   // defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION)
    template <typename Arg>
    stream(Arg& arg, context& ctx)
        : next_layer_(arg),
          core_(ctx.native_handle(), next_layer_.lowest_layer().get_executor().context()) {}
#endif  // defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION)

    /// Destructor.
    /**
     * @note A @c stream object must not be destroyed while there are pending
     * asynchronous operations associated with it.
     */
    ~stream() {}

    /// Get the executor associated with the object.
    /**
     * This function may be used to obtain the executor object that the stream
     * uses to dispatch handlers for asynchronous operations.
     *
     * @return A copy of the executor that stream will use to dispatch handlers.
     */
    executor_type get_executor() ASIO_NOEXCEPT {
        return next_layer_.lowest_layer().get_executor();
    }

#if !defined(ASIO_NO_DEPRECATED)
    /// (Deprecated: Use get_executor().) Get the io_context associated with the
    /// object.
    asio::io_context& get_io_context() {
        return next_layer_.lowest_layer().get_io_context();
    }

    /// (Deprecated: Use get_executor().) Get the io_context associated with the
    /// object.
    asio::io_context& get_io_service() {
        return next_layer_.lowest_layer().get_io_service();
    }
#endif  // !defined(ASIO_NO_DEPRECATED)

    /// Get the underlying implementation in the native type.
    /**
     * This function may be used to obtain the underlying implementation of the
     * context. This is intended to allow access to context functionality that is
     * not otherwise provided.
     *
     * @par Example
     * The native_handle() function returns a pointer of type @c SSL* that is
     * suitable for passing to functions such as @c SSL_get_verify_result and
     * @c SSL_get_peer_certificate:
     * @code
     * asio::ssl::stream<asio:ip::tcp::socket> sock(io_context, ctx);
     *
     * // ... establish connection and perform handshake ...
     *
     * if (X509* cert = SSL_get_peer_certificate(sock.native_handle()))
     * {
     *   if (SSL_get_verify_result(sock.native_handle()) == X509_V_OK)
     *   {
     *     // ...
     *   }
     * }
     * @endcode
     */
    native_handle_type native_handle() {
        return core_.engine_.native_handle();
    }

    /// Gets the sni from the client hello.
    boost::optional<std::string> get_sni() {
        return core_.engine_.get_sni();
    }

    /// Get a reference to the next layer.
    /**
     * This function returns a reference to the next layer in a stack of stream
     * layers.
     *
     * @return A reference to the next layer in the stack of stream layers.
     * Ownership is not transferred to the caller.
     */
    const next_layer_type& next_layer() const {
        return next_layer_;
    }

    /// Get a reference to the next layer.
    /**
     * This function returns a reference to the next layer in a stack of stream
     * layers.
     *
     * @return A reference to the next layer in the stack of stream layers.
     * Ownership is not transferred to the caller.
     */
    next_layer_type& next_layer() {
        return next_layer_;
    }

    /// Get a reference to the lowest layer.
    /**
     * This function returns a reference to the lowest layer in a stack of
     * stream layers.
     *
     * @return A reference to the lowest layer in the stack of stream layers.
     * Ownership is not transferred to the caller.
     */
    lowest_layer_type& lowest_layer() {
        return next_layer_.lowest_layer();
    }

    /// Get a reference to the lowest layer.
    /**
     * This function returns a reference to the lowest layer in a stack of
     * stream layers.
     *
     * @return A reference to the lowest layer in the stack of stream layers.
     * Ownership is not transferred to the caller.
     */
    const lowest_layer_type& lowest_layer() const {
        return next_layer_.lowest_layer();
    }

    /// Perform SSL handshaking.
    /**
     * This function is used to perform SSL handshaking on the stream. The
     * function call will block until handshaking is complete or an error occurs.
     *
     * @param type The type of handshaking to be performed, i.e. as a client or as
     * a server.
     *
     * @throws asio::system_error Thrown on failure.
     */
    void handshake(handshake_type type) {
        asio::error_code ec;
        handshake(type, ec);
        asio::detail::throw_error(ec, "handshake");
    }

    /// Perform SSL handshaking.
    /**
     * This function is used to perform SSL handshaking on the stream. The
     * function call will block until handshaking is complete or an error occurs.
     *
     * @param type The type of handshaking to be performed, i.e. as a client or as
     * a server.
     *
     * @param ec Set to indicate what error occurred, if any.
     */
    ASIO_SYNC_OP_VOID handshake(handshake_type type, asio::error_code& ec) {
        detail::io(next_layer_, core_, detail::handshake_op(type), ec);
        ASIO_SYNC_OP_VOID_RETURN(ec);
    }

    /// Perform SSL handshaking.
    /**
     * This function is used to perform SSL handshaking on the stream. The
     * function call will block until handshaking is complete or an error occurs.
     *
     * @param type The type of handshaking to be performed, i.e. as a client or as
     * a server.
     *
     * @param buffers The buffered data to be reused for the handshake.
     *
     * @throws asio::system_error Thrown on failure.
     */
    template <typename ConstBufferSequence>
    void handshake(handshake_type type, const ConstBufferSequence& buffers) {
        asio::error_code ec;
        handshake(type, buffers, ec);
        asio::detail::throw_error(ec, "handshake");
    }

    /// Perform SSL handshaking.
    /**
     * This function is used to perform SSL handshaking on the stream. The
     * function call will block until handshaking is complete or an error occurs.
     *
     * @param type The type of handshaking to be performed, i.e. as a client or as
     * a server.
     *
     * @param buffers The buffered data to be reused for the handshake.
     *
     * @param ec Set to indicate what error occurred, if any.
     */
    template <typename ConstBufferSequence>
    ASIO_SYNC_OP_VOID handshake(handshake_type type,
                                const ConstBufferSequence& buffers,
                                asio::error_code& ec) {
        detail::io(next_layer_,
                   core_,
                   detail::buffered_handshake_op<ConstBufferSequence>(type, buffers),
                   ec);
        ASIO_SYNC_OP_VOID_RETURN(ec);
    }

    /// Start an asynchronous SSL handshake.
    /**
     * This function is used to asynchronously perform an SSL handshake on the
     * stream. This function call always returns immediately.
     *
     * @param type The type of handshaking to be performed, i.e. as a client or as
     * a server.
     *
     * @param handler The handler to be called when the handshake operation
     * completes. Copies will be made of the handler as required. The equivalent
     * function signature of the handler must be:
     * @code void handler(
     *   const asio::error_code& error // Result of operation.
     * ); @endcode
     */
    template <typename HandshakeHandler>
    ASIO_INITFN_RESULT_TYPE(HandshakeHandler, void(asio::error_code))
    async_handshake(handshake_type type, ASIO_MOVE_ARG(HandshakeHandler) handler) {
        // If you get an error on the following line it means that your handler does
        // not meet the documented type requirements for a HandshakeHandler.
        ASIO_HANDSHAKE_HANDLER_CHECK(HandshakeHandler, handler) type_check;

        asio::async_completion<HandshakeHandler, void(asio::error_code)> init(handler);

        detail::async_io(next_layer_, core_, detail::handshake_op(type), init.completion_handler);

        return init.result.get();
    }

    /// Start an asynchronous SSL handshake.
    /**
     * This function is used to asynchronously perform an SSL handshake on the
     * stream. This function call always returns immediately.
     *
     * @param type The type of handshaking to be performed, i.e. as a client or as
     * a server.
     *
     * @param buffers The buffered data to be reused for the handshake. Although
     * the buffers object may be copied as necessary, ownership of the underlying
     * buffers is retained by the caller, which must guarantee that they remain
     * valid until the handler is called.
     *
     * @param handler The handler to be called when the handshake operation
     * completes. Copies will be made of the handler as required. The equivalent
     * function signature of the handler must be:
     * @code void handler(
     *   const asio::error_code& error, // Result of operation.
     *   std::size_t bytes_transferred // Amount of buffers used in handshake.
     * ); @endcode
     */
    template <typename ConstBufferSequence, typename BufferedHandshakeHandler>
    ASIO_INITFN_RESULT_TYPE(BufferedHandshakeHandler, void(asio::error_code, std::size_t))
    async_handshake(handshake_type type,
                    const ConstBufferSequence& buffers,
                    ASIO_MOVE_ARG(BufferedHandshakeHandler) handler) {
        // If you get an error on the following line it means that your handler does
        // not meet the documented type requirements for a BufferedHandshakeHandler.
        ASIO_BUFFERED_HANDSHAKE_HANDLER_CHECK(BufferedHandshakeHandler, handler) type_check;

        asio::async_completion<BufferedHandshakeHandler, void(asio::error_code, std::size_t)> init(
            handler);

        detail::async_io(next_layer_,
                         core_,
                         detail::buffered_handshake_op<ConstBufferSequence>(type, buffers),
                         init.completion_handler);

        return init.result.get();
    }

    /// Shut down SSL on the stream.
    /**
     * This function is used to shut down SSL on the stream. The function call
     * will block until SSL has been shut down or an error occurs.
     *
     * @throws asio::system_error Thrown on failure.
     */
    void shutdown() {
        asio::error_code ec;
        shutdown(ec);
        asio::detail::throw_error(ec, "shutdown");
    }

    /// Shut down SSL on the stream.
    /**
     * This function is used to shut down SSL on the stream. The function call
     * will block until SSL has been shut down or an error occurs.
     *
     * @param ec Set to indicate what error occurred, if any.
     */
    ASIO_SYNC_OP_VOID shutdown(asio::error_code& ec) {
        detail::io(next_layer_, core_, detail::shutdown_op(), ec);
        ASIO_SYNC_OP_VOID_RETURN(ec);
    }

    /// Asynchronously shut down SSL on the stream.
    /**
     * This function is used to asynchronously shut down SSL on the stream. This
     * function call always returns immediately.
     *
     * @param handler The handler to be called when the handshake operation
     * completes. Copies will be made of the handler as required. The equivalent
     * function signature of the handler must be:
     * @code void handler(
     *   const asio::error_code& error // Result of operation.
     * ); @endcode
     */
    template <typename ShutdownHandler>
    ASIO_INITFN_RESULT_TYPE(ShutdownHandler, void(asio::error_code))
    async_shutdown(ASIO_MOVE_ARG(ShutdownHandler) handler) {
        // If you get an error on the following line it means that your handler does
        // not meet the documented type requirements for a ShutdownHandler.
        ASIO_SHUTDOWN_HANDLER_CHECK(ShutdownHandler, handler) type_check;

        asio::async_completion<ShutdownHandler, void(asio::error_code)> init(handler);

        detail::async_io(next_layer_, core_, detail::shutdown_op(), init.completion_handler);

        return init.result.get();
    }

    /// Write some data to the stream.
    /**
     * This function is used to write data on the stream. The function call will
     * block until one or more bytes of data has been written successfully, or
     * until an error occurs.
     *
     * @param buffers The data to be written.
     *
     * @returns The number of bytes written.
     *
     * @throws asio::system_error Thrown on failure.
     *
     * @note The write_some operation may not transmit all of the data to the
     * peer. Consider using the @ref write function if you need to ensure that all
     * data is written before the blocking operation completes.
     */
    template <typename ConstBufferSequence>
    std::size_t write_some(const ConstBufferSequence& buffers) {
        asio::error_code ec;
        std::size_t n = write_some(buffers, ec);
        asio::detail::throw_error(ec, "write_some");
        return n;
    }

    /// Write some data to the stream.
    /**
     * This function is used to write data on the stream. The function call will
     * block until one or more bytes of data has been written successfully, or
     * until an error occurs.
     *
     * @param buffers The data to be written to the stream.
     *
     * @param ec Set to indicate what error occurred, if any.
     *
     * @returns The number of bytes written. Returns 0 if an error occurred.
     *
     * @note The write_some operation may not transmit all of the data to the
     * peer. Consider using the @ref write function if you need to ensure that all
     * data is written before the blocking operation completes.
     */
    template <typename ConstBufferSequence>
    std::size_t write_some(const ConstBufferSequence& buffers, asio::error_code& ec) {
        return detail::io(next_layer_, core_, detail::write_op<ConstBufferSequence>(buffers), ec);
    }

    /// Start an asynchronous write.
    /**
     * This function is used to asynchronously write one or more bytes of data to
     * the stream. The function call always returns immediately.
     *
     * @param buffers The data to be written to the stream. Although the buffers
     * object may be copied as necessary, ownership of the underlying buffers is
     * retained by the caller, which must guarantee that they remain valid until
     * the handler is called.
     *
     * @param handler The handler to be called when the write operation completes.
     * Copies will be made of the handler as required. The equivalent function
     * signature of the handler must be:
     * @code void handler(
     *   const asio::error_code& error, // Result of operation.
     *   std::size_t bytes_transferred           // Number of bytes written.
     * ); @endcode
     *
     * @note The async_write_some operation may not transmit all of the data to
     * the peer. Consider using the @ref async_write function if you need to
     * ensure that all data is written before the blocking operation completes.
     */
    template <typename ConstBufferSequence, typename WriteHandler>
    ASIO_INITFN_RESULT_TYPE(WriteHandler, void(asio::error_code, std::size_t))
    async_write_some(const ConstBufferSequence& buffers, ASIO_MOVE_ARG(WriteHandler) handler) {
        // If you get an error on the following line it means that your handler does
        // not meet the documented type requirements for a WriteHandler.
        ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check;

        asio::async_completion<WriteHandler, void(asio::error_code, std::size_t)> init(handler);

        detail::async_io(next_layer_,
                         core_,
                         detail::write_op<ConstBufferSequence>(buffers),
                         init.completion_handler);

        return init.result.get();
    }

    /// Read some data from the stream.
    /**
     * This function is used to read data from the stream. The function call will
     * block until one or more bytes of data has been read successfully, or until
     * an error occurs.
     *
     * @param buffers The buffers into which the data will be read.
     *
     * @returns The number of bytes read.
     *
     * @throws asio::system_error Thrown on failure.
     *
     * @note The read_some operation may not read all of the requested number of
     * bytes. Consider using the @ref read function if you need to ensure that the
     * requested amount of data is read before the blocking operation completes.
     */
    template <typename MutableBufferSequence>
    std::size_t read_some(const MutableBufferSequence& buffers) {
        asio::error_code ec;
        std::size_t n = read_some(buffers, ec);
        asio::detail::throw_error(ec, "read_some");
        return n;
    }

    /// Read some data from the stream.
    /**
     * This function is used to read data from the stream. The function call will
     * block until one or more bytes of data has been read successfully, or until
     * an error occurs.
     *
     * @param buffers The buffers into which the data will be read.
     *
     * @param ec Set to indicate what error occurred, if any.
     *
     * @returns The number of bytes read. Returns 0 if an error occurred.
     *
     * @note The read_some operation may not read all of the requested number of
     * bytes. Consider using the @ref read function if you need to ensure that the
     * requested amount of data is read before the blocking operation completes.
     */
    template <typename MutableBufferSequence>
    std::size_t read_some(const MutableBufferSequence& buffers, asio::error_code& ec) {
        return detail::io(next_layer_, core_, detail::read_op<MutableBufferSequence>(buffers), ec);
    }

    /// Start an asynchronous read.
    /**
     * This function is used to asynchronously read one or more bytes of data from
     * the stream. The function call always returns immediately.
     *
     * @param buffers The buffers into which the data will be read. Although the
     * buffers object may be copied as necessary, ownership of the underlying
     * buffers is retained by the caller, which must guarantee that they remain
     * valid until the handler is called.
     *
     * @param handler The handler to be called when the read operation completes.
     * Copies will be made of the handler as required. The equivalent function
     * signature of the handler must be:
     * @code void handler(
     *   const asio::error_code& error, // Result of operation.
     *   std::size_t bytes_transferred           // Number of bytes read.
     * ); @endcode
     *
     * @note The async_read_some operation may not read all of the requested
     * number of bytes. Consider using the @ref async_read function if you need to
     * ensure that the requested amount of data is read before the asynchronous
     * operation completes.
     */
    template <typename MutableBufferSequence, typename ReadHandler>
    ASIO_INITFN_RESULT_TYPE(ReadHandler, void(asio::error_code, std::size_t))
    async_read_some(const MutableBufferSequence& buffers, ASIO_MOVE_ARG(ReadHandler) handler) {
        // If you get an error on the following line it means that your handler does
        // not meet the documented type requirements for a ReadHandler.
        ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check;

        asio::async_completion<ReadHandler, void(asio::error_code, std::size_t)> init(handler);

        detail::async_io(next_layer_,
                         core_,
                         detail::read_op<MutableBufferSequence>(buffers),
                         init.completion_handler);

        return init.result.get();
    }

    asio::mutable_buffer& getCoreOutputBuffer() {
        return core_.output_;
    }

private:
    Stream next_layer_;
    detail::stream_core core_;
};

}  // namespace ssl
}  // namespace asio

#include <asio/detail/pop_options.hpp>

#endif  // ASIO_SSL_STREAM_HPP
